Introduction to Leaflet in R

Basic Usage

Map Widget

create map widget with leaflet()

load in the data. We’ll use the beetle data again.

beetle_url <- "https://raw.githubusercontent.com/dyerlab/ENVS-Lectures/master/data/Araptus_Disperal_Bias.csv"
beetle <- read.csv(beetle_url)

beetle <- beetle %>%
   st_as_sf( coords=c("Longitude","Latitude"), crs=4326 )

Basemaps

Add a basemap with addTiles(). By default OpenStreetMap tiles are used.

Base maps are not required. Without a basemap your map will be floating in space. Using a basemap adds context, especially for point data

leaflet() %>%
  setView(lng = -77.5, lat = 37.5, zoom = 10) %>% #not necessary to set your view
  addTiles()

This is a blank map because we haven’t added any layers

Third party tiles

Use addProviderTiles() function to use a different basemap. Think about what information you are trying to convey when choosing a basemap. Will extra detail be helpful or will it distract from your data? Most third party base map styles have a labeled and an unlabeled option.

Base maps can be combined if, for example, you like the style of one basemap but the labels of another basemap. You can also include multiple basemap layers and let the viewer select their prefered map. We’ll see how to do this later.

See here for all options

leaflet() %>%
  setView(lng = -77.5, lat = 37.5, zoom = 10) %>% #not necessary to set your view
  addProviderTiles(providers$CartoDB.Positron)

Data Objects

Both leaflet() and each map layer have a data = parameter. Spatial data can be in the form of:

  • dataframe with lat/lng columns
  • sp objects
  • sf objects
  • maps package map() objects

Leaflet only uses WGS84 for displaying data. Leaflet can project coordinates automatically, but projecting your data to WGS84 (crs = 4326) is a good habit to get into.

Data can be passed through the leaflet() function or through the map layers.

Here we define our data in the leaflet() function

leaflet(beetle) %>%
  addTiles() %>%
  addCircles()

Here we define the data in the map layer (addCircles())

leaflet() %>%
  addTiles() %>%
  addCircles(data = beetle)

Markers

Points can be plotted using addMarkers() or addCircles()

Default markers

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers()

zoom in/out and notice that the markers stay the same size

Makers can be customize using options = markerOptions()

Custom Markers

bug <- icons(
  iconUrl = "https://www.pngfind.com/pngs/m/14-144860_beetle-bug-png-transparent-image-bug-png-png.png",
  iconWidth = 20,
  iconHeight = 20
)

Use your custom marker in the icons = of the addMarkers() function

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addMarkers(icon = bug)

Marker from a library

link to Font Awesome library

makeAwesomeIcon() allows you to select your icon and edit its color ect.

addAwesomeMarkers() allows you to add your custom icon to the map

fa_bug <- makeAwesomeIcon(icon = "bug", library = "fa", 
                          markerColor = "cadetblue", iconColor = "beige")

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addAwesomeMarkers(icon = fa_bug)
NA

Notice that our custom icon using makeAwesomeIcon() and addAwsomeMarkers() displays the icon on top of a marker. The custom icon using addMarkers() displays directly on the map

Pop-ups and Labels

Pop-ups

Pop-ups can be added as a stand-alone feature using addPopups(), or add to appear when a shape is clicked

Stand-alone

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  setView(lng = -111, lat = 25, zoom = 7) %>%
  addPopups(lng = -111, lat = 25, paste0("beetles live here"))

Notice that the pop-up is its own leaflet layer

As a marker option

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addAwesomeMarkers(icon = fa_bug, popup = paste0("Site:", beetle$Site))

Notice that this time we define our pop-up inside the maker layer, not as its own layer

Labels

Use label = to add a label displayed on a mouse over

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addAwesomeMarkers(icon = fa_bug, label = paste0("Site:", beetle$Site))

Labels and Pop-ups can be customized using the labelOptions = labelOptions() function. You can define the text size, color, font, box border, box shadow, ect.

Adding Circles, Lines and Polygons

Circles, lines and polygons behave very similarly in leaflet. For this lecture we will continue using our beetle point data. There is an example using polygons at the end of the lecture.

addCircles()

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles()

If you zoom in far enough you will see that the size of the circles changes with the zoom level. This feature will become more apparent in later examples.

Change the size (radius)

In this example the size of the circles is proportional to the number of males at each site. The value is multiplied by 500 so the different sizes can be easily visualized when zoomed out.

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(radius = ~Males *500, stroke = FALSE, fillOpacity = .5)

Color

color will define the stroke and the fill color unless fillColor is specified

In this example the color of the circles is now proportional to the male to female ratio (beetle$MFRatio) of each site. Our palette is defined before hand using colorNumeric() and stored as pal.

pal <- colorNumeric(
  palette = "RdBu", # Red to Blue palette
  domain = beetle$MFRatio
)


leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(color = ~pal(MFRatio), fillOpacity = .7, radius = 15000, stroke = FALSE,
             label = paste0(beetle$MFRatio))

Darker blue circles represent sites with a larger male to female ratio. Darker red circles represent sites with a smaller male to female ratio. We will see how to add a legend later in the lecture.

Highlight

Individual shapes can be highlighted when hovered over using highlightOptions within the shape layer.

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(fillColor = pal(beetle$MFRatio), fillOpacity = .7, radius = 15000, 
             weight = 1, color = "black",
  highlightOptions = highlightOptions(color = "yellow", weight = 4,
      bringToFront = TRUE))

Hover your mouse over different circles and notice the the stroke color changes from black to yellow and becomes thicker. If circles overlap, hovering now brings that circle in front of the others.

Map Groups

Assign layers to groups using group =. Assigning groups allows you to show/hide layers or control the visibility of layers through the function addLayersControl()

A group can be made up a a single layer or multiple layers, but each layer can only belong to one group.

Layer Controls

Once your data layers have been assigned to groups you can use addLayersControl() to control individual layers

In this example sites with more males than females are blue and sites with more females than males are pink. Because we want to control the two different types of circles separately, they need to be added as individual layers. The group = is defined inside each addCircles() layer.

Under the addLayersControl() layer we can select which groups to control. In this case I want to be able to turn the layers on/off. overlayGroups can be individually checked or unchecked.


leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(data = (beetle %>% filter(Males > Females)), 
             fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", radius = 10000, weight = 1,
             group = "males") %>%
  addCircles(data = (beetle %>% filter(Males < Females)), 
             fillColor = "pink", fillOpacity = 1, 
             color = "red", radius = 10000, weight = 1,
             group = "females") %>%
  addLayersControl(overlayGroups = c("males", "females"),
                   options = layersControlOptions(collapsed = FALSE))
NA

Base Groups

baseGroups can only be viewed one group at a time and one group is always selected (can’t turn them all off)

Base groups are useful when providing viewers a choice of basemaps. In the example below, three different tile layers are added, each their own group. Now in addLayersControl() we can include these basemap groups in the baseGroups.

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron, group = "Default") %>%
  addProviderTiles(providers$OpenStreetMap, group = "Open Street Maps") %>%
  addProviderTiles(providers$Stamen.Toner, group = "Stamen Toner") %>%
  addCircles(data = (beetle %>% filter(Males > Females)), 
             fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", radius = 10000, weight = 1,
             group = "males") %>%
  addCircles(data = (beetle %>% filter(Males < Females)), 
             fillColor = "pink", fillOpacity = 1, 
             color = "red", radius = 10000, weight = 1,
             group = "females") %>%
  addLayersControl(overlayGroups = c("males", "females"),
                   options = layersControlOptions(collapsed = FALSE),
                   baseGroups = c("Default", "Open Street Maps", "Stamen Toner"))

Notice the difference between overlay groups (circles) and base groups (basemaps)

Zoom Levels

Want to display more detail when the map in zoomed in and less detail when the map is zoomed out? This is done with zoomLevels in the groupOptions()

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", radius = 10000, weight = 1,
             group = "beetles") %>%
  groupOptions("beetles", zoomLevels = 5:8)

Zoom in and out to watch the layer turn on/off

Clustering

When there are a larger number of makers on a map you can cluster them using clusterOpions =

A common example of clustering is the Dominion Power outage map.

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", weight = 1,
             group = "beetles",
             clusterOptions = markerClusterOptions()) 

Hovering over a cluster marker with your mouse allows you to see the coverage of the cluster

Click on a cluster to zoom in to the cluster bounds

All of these features can be changed markerClusterOptions. markerClusterOptions also allows you to freeze the clustering at a defined zoom level with freezeAtZoom =

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", weight = 1,
             group = "beetles",
             clusterOptions = markerClusterOptions(
               showCoverageOnHover = FALSE,
               freezeAtZoom = 6)) 

Now the clusters stay the same regardless of zoom level. When the cluster is clicked you can see each point included in the cluster

Legends

Legends are added using addLegend()

The example below shows a legend with categorical data

leaflet() %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(data = (beetle %>% filter(Males > Females)), 
             fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", radius = 10000, weight = 1,
             group = "males") %>%
  addCircles(data = (beetle %>% filter(Males < Females)), 
             fillColor = "pink", fillOpacity = 1, 
             color = "red", radius = 10000, weight = 1,
             group = "females") %>%
  addLegend( colors = c("lightblue", "pink"),
             labels = c("More Males", "More Females"),
             opacity = 1)

The next example shows a legend with continuous data. Notice that pal is used this time instead of color and values instead of labels

pal <- colorNumeric(
  palette = "RdBu",
  domain = beetle$MFRatio)

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircles(fillColor = ~pal(MFRatio), fillOpacity = .7, radius = 15000, 
             weight = 1, color = "grey") %>%
  addLegend(pal = pal, values = ~MFRatio,
            title = "Male to Female Ratio")

Scale Bars

Scale bars are added using addScaleBar(). The scale bar will adjust itself as you zoom in and out

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", weight = 1) %>%
  addScaleBar(position = "bottomright")
NA

You can choose to use metric and/or imperial units and adjust the scale bar width using options = scaleBarOptions.

Leaflet Extras Package

The leaflet.extras package allows for additional functionality via leaflet plugins. We’ll take a look at three examples here, but there are tons of plugins to choose from

Reset

addResetMapButton() resets you map to the original view and zoom level

library(leaflet.extras)

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", weight = 1) %>%
  addResetMapButton()

Move the map and zoom in/out, then press the reset button to make the map return to its starting view and zoom level

Search Bars

addSearchOSM() and addSearchGoogle() allow you to search for locations on the map

Try searching for “Cabo San Lucas”

Draw Toolbar

addDrawToolbar() allows you to draw points, lines and polygons on map

leaflet(beetle) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(fillColor = "lightblue", fillOpacity = 1, 
             color = "blue", weight = 1) %>%
  addDrawToolbar(
    editOptions = editToolbarOptions(
      selectedPathOptions = selectedPathOptions() )) %>%
  removeDrawToolbar()

Try drawing and then deleting a shape from your map

addStlyeEditor() allows you to edit the appearance of shapes

Making a Choropleth Map

Lines and Polygons work in a very similar manner to circles

For this example we’ll use census tracts from the tigris package

library(tidyverse)
library(tigris)
options(tigris_use_cache = TRUE)

tracts <- tracts("VA", "Richmond city") %>%
  st_transform(4326)

Defining the color palette using the land area of each tract. Area is given in square meters. To convert to square miles we divide our area by 2.59e+6

pal <- colorNumeric(palette = "Blues",
                    domain = tracts$ALAND/2.59e+6) 

The polygon colors are assigned using fillColor = ~pal(ALAND/2.59e+6)

Labels, legend and scale bar are added to give the map a more polished finish

leaflet(tracts) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addPolygons(fillColor = ~pal(ALAND/2.59e+6),  fillOpacity = 1, smoothFactor = .2,
              color = "darkgrey", weight = 1,
                highlightOptions = highlightOptions(color = "white", weight = 2, opacity = 1,
                bringToFront = TRUE),
              label = paste(round(tracts$ALAND/2.59e+6, digits = 2), "sq. mi.")
              ) %>%
  addLegend(pal = pal, values = ~ALAND/2.59e+6, title = "Area (sq. miles)") %>%
  addScaleBar(position = "bottomright")

The map is now shaded so that the larger census tracts are darker blue and the smallest tracts are lighter blue

LS0tDQp0aXRsZTogIkxlYWZsZXQiDQphdXRob3I6ICJDaGFyaXMgRGVhZHd5bGVyIg0KZGF0ZTogIjMvMTUvMjAyMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KDQpsaWJyYXJ5KGxlYWZsZXQpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KW0ludHJvZHVjdGlvbiB0byBMZWFmbGV0IGluIFJdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC8pDQoNCiMjIEJhc2ljIFVzYWdlDQoNCiMjIyBNYXAgV2lkZ2V0DQoNCioqY3JlYXRlIG1hcCB3aWRnZXQgd2l0aCBgbGVhZmxldCgpYCoqDQoNCg0KbG9hZCBpbiB0aGUgZGF0YS4gV2UnbGwgdXNlIHRoZSBiZWV0bGUgZGF0YSBhZ2Fpbi4NCmBgYHtyfQ0KYmVldGxlX3VybCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2R5ZXJsYWIvRU5WUy1MZWN0dXJlcy9tYXN0ZXIvZGF0YS9BcmFwdHVzX0Rpc3BlcmFsX0JpYXMuY3N2Ig0KYmVldGxlIDwtIHJlYWQuY3N2KGJlZXRsZV91cmwpDQoNCmJlZXRsZSA8LSBiZWV0bGUgJT4lDQogICBzdF9hc19zZiggY29vcmRzPWMoIkxvbmdpdHVkZSIsIkxhdGl0dWRlIiksIGNycz00MzI2ICkNCmBgYA0KDQojIyMgQmFzZW1hcHMNCg0KQWRkIGEgYmFzZW1hcCB3aXRoIGBhZGRUaWxlcygpYC4gQnkgZGVmYXVsdCBPcGVuU3RyZWV0TWFwIHRpbGVzIGFyZSB1c2VkLiANCg0KQmFzZSBtYXBzIGFyZSBub3QgcmVxdWlyZWQuIFdpdGhvdXQgYSBiYXNlbWFwIHlvdXIgbWFwIHdpbGwgYmUgZmxvYXRpbmcgaW4gc3BhY2UuIFVzaW5nIGEgYmFzZW1hcCBhZGRzIGNvbnRleHQsIGVzcGVjaWFsbHkgZm9yIHBvaW50IGRhdGENCg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lDQogIHNldFZpZXcobG5nID0gLTc3LjUsIGxhdCA9IDM3LjUsIHpvb20gPSAxMCkgJT4lICNub3QgbmVjZXNzYXJ5IHRvIHNldCB5b3VyIHZpZXcNCiAgYWRkVGlsZXMoKQ0KYGBgDQpUaGlzIGlzIGEgYmxhbmsgbWFwIGJlY2F1c2Ugd2UgaGF2ZW4ndCBhZGRlZCBhbnkgbGF5ZXJzDQoNCg0KIyMjIFRoaXJkIHBhcnR5IHRpbGVzDQoNClVzZSBgYWRkUHJvdmlkZXJUaWxlcygpYCBmdW5jdGlvbiB0byB1c2UgYSBkaWZmZXJlbnQgYmFzZW1hcC4gIFRoaW5rIGFib3V0IHdoYXQgaW5mb3JtYXRpb24geW91IGFyZSB0cnlpbmcgdG8gY29udmV5IHdoZW4gY2hvb3NpbmcgYSBiYXNlbWFwLiBXaWxsIGV4dHJhIGRldGFpbCBiZSBoZWxwZnVsIG9yIHdpbGwgaXQgZGlzdHJhY3QgZnJvbSB5b3VyIGRhdGE/IE1vc3QgdGhpcmQgcGFydHkgYmFzZSBtYXAgc3R5bGVzIGhhdmUgYSBsYWJlbGVkIGFuZCBhbiB1bmxhYmVsZWQgb3B0aW9uLiANCg0KQmFzZSBtYXBzIGNhbiBiZSBjb21iaW5lZCBpZiwgZm9yIGV4YW1wbGUsIHlvdSBsaWtlIHRoZSBzdHlsZSBvZiBvbmUgYmFzZW1hcCBidXQgdGhlIGxhYmVscyBvZiBhbm90aGVyIGJhc2VtYXAuIFlvdSBjYW4gYWxzbyBpbmNsdWRlIG11bHRpcGxlIGJhc2VtYXAgbGF5ZXJzIGFuZCBsZXQgdGhlIHZpZXdlciBzZWxlY3QgdGhlaXIgcHJlZmVyZWQgbWFwLiBXZSdsbCBzZWUgaG93IHRvIGRvIHRoaXMgbGF0ZXIuDQoNCltTZWUgaGVyZV0oaHR0cDovL2xlYWZsZXQtZXh0cmFzLmdpdGh1Yi5pby9sZWFmbGV0LXByb3ZpZGVycy9wcmV2aWV3L2luZGV4Lmh0bWwpIGZvciBhbGwgb3B0aW9ucw0KDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtNzcuNSwgbGF0ID0gMzcuNSwgem9vbSA9IDEwKSAlPiUgI25vdCBuZWNlc3NhcnkgdG8gc2V0IHlvdXIgdmlldw0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKQ0KYGBgDQogICANCg0KIyMjIERhdGEgT2JqZWN0cw0KDQpCb3RoIGBsZWFmbGV0KClgIGFuZCBlYWNoIG1hcCBsYXllciBoYXZlIGEgYGRhdGEgPWAgcGFyYW1ldGVyLiBTcGF0aWFsIGRhdGEgY2FuIGJlIGluIHRoZSBmb3JtIG9mOg0KDQotIGRhdGFmcmFtZSB3aXRoIGxhdC9sbmcgY29sdW1ucw0KLSBgc3BgIG9iamVjdHMNCi0gYHNmYCBvYmplY3RzDQotIGBtYXBzYCBwYWNrYWdlIGBtYXAoKWAgb2JqZWN0cw0KDQpMZWFmbGV0IG9ubHkgdXNlcyAqKldHUzg0KiogZm9yIGRpc3BsYXlpbmcgZGF0YS4gTGVhZmxldCBjYW4gcHJvamVjdCBjb29yZGluYXRlcyBhdXRvbWF0aWNhbGx5LCBidXQgcHJvamVjdGluZyB5b3VyIGRhdGEgdG8gV0dTODQgKGNycyA9IDQzMjYpIGlzIGEgZ29vZCBoYWJpdCB0byBnZXQgaW50by4NCg0KDQpEYXRhIGNhbiBiZSBwYXNzZWQgdGhyb3VnaCB0aGUgYGxlYWZsZXQoKWAgZnVuY3Rpb24gb3IgdGhyb3VnaCB0aGUgbWFwIGxheWVycy4NCg0KSGVyZSB3ZSBkZWZpbmUgb3VyIGRhdGEgaW4gdGhlIGBsZWFmbGV0KClgIGZ1bmN0aW9uDQpgYGB7cn0NCmxlYWZsZXQoYmVldGxlKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUNCiAgYWRkQ2lyY2xlcygpDQpgYGANCiAgIA0KDQpIZXJlIHdlIGRlZmluZSB0aGUgZGF0YSBpbiB0aGUgbWFwIGxheWVyIChgYWRkQ2lyY2xlcygpYCkNCmBgYHtyfQ0KbGVhZmxldCgpICU+JQ0KICBhZGRUaWxlcygpICU+JQ0KICBhZGRDaXJjbGVzKGRhdGEgPSBiZWV0bGUpDQpgYGANCg0KDQojIyBNYXJrZXJzDQoNClBvaW50cyBjYW4gYmUgcGxvdHRlZCB1c2luZyBgYWRkTWFya2VycygpYCBvciBgYWRkQ2lyY2xlcygpYA0KDQotIE1hcmtlcnMgc3RheSB0aGUgc2FtZSBzaXplIHJlZ2FyZGxlc3Mgb2Ygem9vbSBsZXZlbA0KDQotIENpcmNsZXMgc2NhbGUgd2l0aCB0aGUgbWFwDQoNCg0KKipEZWZhdWx0IG1hcmtlcnMqKg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRNYXJrZXJzKCkNCmBgYA0Kem9vbSBpbi9vdXQgYW5kIG5vdGljZSB0aGF0IHRoZSBtYXJrZXJzIHN0YXkgdGhlIHNhbWUgc2l6ZQ0KDQpNYWtlcnMgY2FuIGJlIGN1c3RvbWl6ZSB1c2luZyBgb3B0aW9ucyA9IG1hcmtlck9wdGlvbnMoKWANCg0KDQoNCg0KKipDdXN0b20gTWFya2VycyoqDQoNCi0gQ3VzdG9tIG1hcmtlcnMgY2FuIGJlIG1hZGUgdXNpbmcgYSB1cmwvaW1hZ2UgZmlsZSBvciBmcm9tIGxpYnJhcmllcyANCg0KLSBJdCBpcyBpbXBvcnRhbnQgdG8gZGVmaW5lIHRoZSBoZWlnaHQgYW5kIHdpZHRoIChpbiBwaXhlbHMpIG9mIHlvdXIgaWNvbnMNCg0KYGBge3J9DQpidWcgPC0gaWNvbnMoDQogIGljb25VcmwgPSAiaHR0cHM6Ly93d3cucG5nZmluZC5jb20vcG5ncy9tLzE0LTE0NDg2MF9iZWV0bGUtYnVnLXBuZy10cmFuc3BhcmVudC1pbWFnZS1idWctcG5nLXBuZy5wbmciLA0KICBpY29uV2lkdGggPSAyMCwNCiAgaWNvbkhlaWdodCA9IDIwDQopDQpgYGANCg0KVXNlIHlvdXIgY3VzdG9tIG1hcmtlciBpbiB0aGUgYGljb25zID1gIG9mIHRoZSBgYWRkTWFya2VycygpYCBmdW5jdGlvbg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRNYXJrZXJzKGljb24gPSBidWcpDQpgYGANCg0KKipNYXJrZXIgZnJvbSBhIGxpYnJhcnkqKg0KDQpbbGluayB0byBGb250IEF3ZXNvbWUgbGlicmFyeV0oaHR0cHM6Ly9mb250YXdlc29tZS5jb20vaWNvbnM/ZD1nYWxsZXJ5JnA9MiZtPWZyZWUpDQoNCmBtYWtlQXdlc29tZUljb24oKWAgYWxsb3dzIHlvdSB0byBzZWxlY3QgeW91ciBpY29uIGFuZCBlZGl0IGl0cyBjb2xvciBlY3QuDQoNCmBhZGRBd2Vzb21lTWFya2VycygpYCBhbGxvd3MgeW91IHRvIGFkZCB5b3VyIGN1c3RvbSBpY29uIHRvIHRoZSBtYXANCmBgYHtyfQ0KZmFfYnVnIDwtIG1ha2VBd2Vzb21lSWNvbihpY29uID0gImJ1ZyIsIGxpYnJhcnkgPSAiZmEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWFya2VyQ29sb3IgPSAiY2FkZXRibHVlIiwgaWNvbkNvbG9yID0gImJlaWdlIikNCg0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGZhX2J1ZykNCg0KYGBgDQogICANCk5vdGljZSB0aGF0IG91ciBjdXN0b20gaWNvbiB1c2luZyBgbWFrZUF3ZXNvbWVJY29uKClgIGFuZCBgYWRkQXdzb21lTWFya2VycygpYCBkaXNwbGF5cyB0aGUgaWNvbiBvbiB0b3Agb2YgYSBtYXJrZXIuIFRoZSBjdXN0b20gaWNvbiB1c2luZyBgYWRkTWFya2VycygpYCBkaXNwbGF5cyBkaXJlY3RseSBvbiB0aGUgbWFwDQoNCg0KIyMgUG9wLXVwcyBhbmQgTGFiZWxzDQoNCi0gUG9wLXVwcyBhcHBlYXIvZGlzYXBwZWFyIHdoZW4gY2xpY2tlZA0KDQotIExhYmVscyBhcHBlYXIvZGlzYXBwZWFyIHdoZW4geW91IG1vdXNlIGhvdmVycyBvbiB0aGUgb2JqZWN0DQoNCg0KIyMjIFBvcC11cHMNCg0KUG9wLXVwcyBjYW4gYmUgYWRkZWQgYXMgYSBzdGFuZC1hbG9uZSBmZWF0dXJlIHVzaW5nIGBhZGRQb3B1cHMoKWAsIG9yIGFkZCB0byBhcHBlYXIgd2hlbiBhIHNoYXBlIGlzIGNsaWNrZWQNCg0KDQoqKlN0YW5kLWFsb25lKioNCmBgYHtyfQ0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgc2V0VmlldyhsbmcgPSAtMTExLCBsYXQgPSAyNSwgem9vbSA9IDcpICU+JQ0KICBhZGRQb3B1cHMobG5nID0gLTExMSwgbGF0ID0gMjUsIHBhc3RlMCgiYmVldGxlcyBsaXZlIGhlcmUiKSkNCmBgYA0KTm90aWNlIHRoYXQgdGhlIHBvcC11cCBpcyBpdHMgb3duIGxlYWZsZXQgbGF5ZXINCiANCiANCg0KKipBcyBhIG1hcmtlciBvcHRpb24qKg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRBd2Vzb21lTWFya2VycyhpY29uID0gZmFfYnVnLCBwb3B1cCA9IHBhc3RlMCgiU2l0ZToiLCBiZWV0bGUkU2l0ZSkpDQpgYGANCk5vdGljZSB0aGF0IHRoaXMgdGltZSB3ZSBkZWZpbmUgb3VyIHBvcC11cCBpbnNpZGUgdGhlIG1ha2VyIGxheWVyLCBub3QgYXMgaXRzIG93biBsYXllcg0KDQojIyMgTGFiZWxzDQoNClVzZSBgbGFiZWwgPSBgIHRvIGFkZCBhIGxhYmVsIGRpc3BsYXllZCBvbiBhIG1vdXNlIG92ZXINCmBgYHtyfQ0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgYWRkQXdlc29tZU1hcmtlcnMoaWNvbiA9IGZhX2J1ZywgbGFiZWwgPSBwYXN0ZTAoIlNpdGU6IiwgYmVldGxlJFNpdGUpKQ0KYGBgDQoNCg0KTGFiZWxzIGFuZCBQb3AtdXBzIGNhbiBiZSBjdXN0b21pemVkIHVzaW5nIHRoZSBgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKClgIGZ1bmN0aW9uLiBZb3UgY2FuIGRlZmluZSB0aGUgdGV4dCBzaXplLCBjb2xvciwgZm9udCwgYm94IGJvcmRlciwgYm94IHNoYWRvdywgZWN0Lg0KDQojIyBBZGRpbmcgQ2lyY2xlcywgTGluZXMgYW5kIFBvbHlnb25zDQoNCi0gYGFkZENpcmNsZXMoKWANCg0KLSBgYWRkTGluZXMoKWANCg0KLSBgYWRkUG9seWdvbnMoKWANCg0KQ2lyY2xlcywgbGluZXMgYW5kIHBvbHlnb25zIGJlaGF2ZSB2ZXJ5IHNpbWlsYXJseSBpbiBsZWFmbGV0LiBGb3IgdGhpcyBsZWN0dXJlIHdlIHdpbGwgY29udGludWUgdXNpbmcgb3VyIGJlZXRsZSBwb2ludCBkYXRhLiBUaGVyZSBpcyBhbiBleGFtcGxlIHVzaW5nIHBvbHlnb25zIGF0IHRoZSBlbmQgb2YgdGhlIGxlY3R1cmUuDQoNCg0KIyMjIGBhZGRDaXJjbGVzKClgDQpgYGB7cn0NCmxlYWZsZXQoYmVldGxlKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZXMoKQ0KYGBgDQpJZiB5b3Ugem9vbSBpbiBmYXIgZW5vdWdoIHlvdSB3aWxsIHNlZSB0aGF0IHRoZSBzaXplIG9mIHRoZSBjaXJjbGVzIGNoYW5nZXMgd2l0aCB0aGUgem9vbSBsZXZlbC4gVGhpcyBmZWF0dXJlIHdpbGwgYmVjb21lIG1vcmUgYXBwYXJlbnQgaW4gbGF0ZXIgZXhhbXBsZXMuDQoNCiMjIyBDaGFuZ2UgdGhlIHNpemUgKGByYWRpdXNgKQ0KDQpJbiB0aGlzIGV4YW1wbGUgdGhlIHNpemUgb2YgdGhlIGNpcmNsZXMgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBudW1iZXIgb2YgbWFsZXMgYXQgZWFjaCBzaXRlLiBUaGUgdmFsdWUgaXMgbXVsdGlwbGllZCBieSA1MDAgc28gdGhlIGRpZmZlcmVudCBzaXplcyBjYW4gYmUgZWFzaWx5IHZpc3VhbGl6ZWQgd2hlbiB6b29tZWQgb3V0Lg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVzKHJhZGl1cyA9IH5NYWxlcyAqNTAwLCBzdHJva2UgPSBGQUxTRSwgZmlsbE9wYWNpdHkgPSAuNSkNCmBgYA0KDQoNCiMjIyBDb2xvcg0KDQpgY29sb3JgIHdpbGwgZGVmaW5lIHRoZSBzdHJva2UgYW5kIHRoZSBmaWxsIGNvbG9yIHVubGVzcyBgZmlsbENvbG9yYCBpcyBzcGVjaWZpZWQNCg0KSW4gdGhpcyBleGFtcGxlIHRoZSBjb2xvciBvZiB0aGUgY2lyY2xlcyBpcyBub3cgcHJvcG9ydGlvbmFsIHRvIHRoZSBtYWxlIHRvIGZlbWFsZSByYXRpbyAoYGJlZXRsZSRNRlJhdGlvYCkgb2YgZWFjaCBzaXRlLiBPdXIgcGFsZXR0ZSBpcyBkZWZpbmVkIGJlZm9yZSBoYW5kIHVzaW5nIGBjb2xvck51bWVyaWMoKWAgYW5kIHN0b3JlZCBhcyBgcGFsYC4NCmBgYHtyfQ0KcGFsIDwtIGNvbG9yTnVtZXJpYygNCiAgcGFsZXR0ZSA9ICJSZEJ1IiwgIyBSZWQgdG8gQmx1ZSBwYWxldHRlDQogIGRvbWFpbiA9IGJlZXRsZSRNRlJhdGlvDQopDQoNCg0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgYWRkQ2lyY2xlcyhjb2xvciA9IH5wYWwoTUZSYXRpbyksIGZpbGxPcGFjaXR5ID0gLjcsIHJhZGl1cyA9IDE1MDAwLCBzdHJva2UgPSBGQUxTRSwNCiAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlMChiZWV0bGUkTUZSYXRpbykpDQpgYGANCkRhcmtlciBibHVlIGNpcmNsZXMgcmVwcmVzZW50IHNpdGVzIHdpdGggYSBsYXJnZXIgbWFsZSB0byBmZW1hbGUgcmF0aW8uIERhcmtlciByZWQgY2lyY2xlcyByZXByZXNlbnQgc2l0ZXMgd2l0aCBhIHNtYWxsZXIgbWFsZSB0byBmZW1hbGUgcmF0aW8uIFdlIHdpbGwgc2VlIGhvdyB0byBhZGQgYSBsZWdlbmQgbGF0ZXIgaW4gdGhlIGxlY3R1cmUuDQoNCg0KIyMjIEhpZ2hsaWdodA0KDQpJbmRpdmlkdWFsIHNoYXBlcyBjYW4gYmUgaGlnaGxpZ2h0ZWQgd2hlbiBob3ZlcmVkIG92ZXIgdXNpbmcgYGhpZ2hsaWdodE9wdGlvbnNgIHdpdGhpbiB0aGUgc2hhcGUgbGF5ZXIuDQpgYGB7cn0NCmxlYWZsZXQoYmVldGxlKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZXMoZmlsbENvbG9yID0gcGFsKGJlZXRsZSRNRlJhdGlvKSwgZmlsbE9wYWNpdHkgPSAuNywgcmFkaXVzID0gMTUwMDAsIA0KICAgICAgICAgICAgIHdlaWdodCA9IDEsIGNvbG9yID0gImJsYWNrIiwNCiAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMoY29sb3IgPSAieWVsbG93Iiwgd2VpZ2h0ID0gNCwNCiAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpKQ0KYGBgDQpIb3ZlciB5b3VyIG1vdXNlIG92ZXIgZGlmZmVyZW50IGNpcmNsZXMgYW5kIG5vdGljZSB0aGUgdGhlIHN0cm9rZSBjb2xvciBjaGFuZ2VzIGZyb20gYmxhY2sgdG8geWVsbG93IGFuZCBiZWNvbWVzIHRoaWNrZXIuIElmIGNpcmNsZXMgb3ZlcmxhcCwgaG92ZXJpbmcgbm93IGJyaW5ncyB0aGF0IGNpcmNsZSBpbiBmcm9udCBvZiB0aGUgb3RoZXJzLg0KDQojIyBNYXAgR3JvdXBzDQoNCkFzc2lnbiBsYXllcnMgdG8gZ3JvdXBzIHVzaW5nIGBncm91cCA9IGAuIEFzc2lnbmluZyBncm91cHMgYWxsb3dzIHlvdSB0byBzaG93L2hpZGUgbGF5ZXJzIG9yIGNvbnRyb2wgdGhlIHZpc2liaWxpdHkgb2YgbGF5ZXJzIHRocm91Z2ggdGhlIGZ1bmN0aW9uIGBhZGRMYXllcnNDb250cm9sKClgDQoNCkEgZ3JvdXAgY2FuIGJlIG1hZGUgdXAgYSBhIHNpbmdsZSBsYXllciBvciBtdWx0aXBsZSBsYXllcnMsIGJ1dCBlYWNoIGxheWVyIGNhbiBvbmx5IGJlbG9uZyB0byBvbmUgZ3JvdXAuDQoNCiMjIyBMYXllciBDb250cm9scw0KDQpPbmNlIHlvdXIgZGF0YSBsYXllcnMgaGF2ZSBiZWVuIGFzc2lnbmVkIHRvIGdyb3VwcyB5b3UgY2FuIHVzZSBgYWRkTGF5ZXJzQ29udHJvbCgpYCB0byBjb250cm9sIGluZGl2aWR1YWwgbGF5ZXJzDQoNCg0KSW4gdGhpcyBleGFtcGxlIHNpdGVzIHdpdGggbW9yZSBtYWxlcyB0aGFuIGZlbWFsZXMgYXJlIGJsdWUgYW5kIHNpdGVzIHdpdGggbW9yZSBmZW1hbGVzIHRoYW4gbWFsZXMgYXJlIHBpbmsuIEJlY2F1c2Ugd2Ugd2FudCB0byBjb250cm9sIHRoZSB0d28gZGlmZmVyZW50IHR5cGVzIG9mIGNpcmNsZXMgc2VwYXJhdGVseSwgdGhleSBuZWVkIHRvIGJlIGFkZGVkIGFzIGluZGl2aWR1YWwgbGF5ZXJzLiBUaGUgYGdyb3VwID1gIGlzIGRlZmluZWQgaW5zaWRlIGVhY2ggYGFkZENpcmNsZXMoKWAgbGF5ZXIuIA0KDQpVbmRlciB0aGUgYGFkZExheWVyc0NvbnRyb2woKWAgbGF5ZXIgd2UgY2FuIHNlbGVjdCB3aGljaCBncm91cHMgdG8gY29udHJvbC4gSW4gdGhpcyBjYXNlIEkgd2FudCB0byBiZSBhYmxlIHRvIHR1cm4gdGhlIGxheWVycyBvbi9vZmYuIGBvdmVybGF5R3JvdXBzYCBjYW4gYmUgaW5kaXZpZHVhbGx5IGNoZWNrZWQgb3IgdW5jaGVja2VkLg0KYGBge3J9DQoNCmxlYWZsZXQoKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA+IEZlbWFsZXMpKSwgDQogICAgICAgICAgICAgZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgDQogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gIm1hbGVzIikgJT4lDQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA8IEZlbWFsZXMpKSwgDQogICAgICAgICAgICAgZmlsbENvbG9yID0gInBpbmsiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gImZlbWFsZXMiKSAlPiUNCiAgYWRkTGF5ZXJzQ29udHJvbChvdmVybGF5R3JvdXBzID0gYygibWFsZXMiLCAiZmVtYWxlcyIpLA0KICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsYXllcnNDb250cm9sT3B0aW9ucyhjb2xsYXBzZWQgPSBGQUxTRSkpDQoNCmBgYA0KICAgDQojIyMgQmFzZSBHcm91cHMNCg0KYGJhc2VHcm91cHNgIGNhbiBvbmx5IGJlIHZpZXdlZCBvbmUgZ3JvdXAgYXQgYSB0aW1lIGFuZCBvbmUgZ3JvdXAgaXMgYWx3YXlzIHNlbGVjdGVkIChjYW4ndCB0dXJuIHRoZW0gYWxsIG9mZikNCg0KQmFzZSBncm91cHMgYXJlIHVzZWZ1bCB3aGVuIHByb3ZpZGluZyB2aWV3ZXJzIGEgY2hvaWNlIG9mIGJhc2VtYXBzLiBJbiB0aGUgZXhhbXBsZSBiZWxvdywgdGhyZWUgZGlmZmVyZW50IHRpbGUgbGF5ZXJzIGFyZSBhZGRlZCwgZWFjaCB0aGVpciBvd24gZ3JvdXAuIE5vdyBpbiBgYWRkTGF5ZXJzQ29udHJvbCgpYCB3ZSBjYW4gaW5jbHVkZSB0aGVzZSBiYXNlbWFwIGdyb3VwcyBpbiB0aGUgYGJhc2VHcm91cHNgLg0KYGBge3J9DQpsZWFmbGV0KCkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24sIGdyb3VwID0gIkRlZmF1bHQiKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkT3BlblN0cmVldE1hcCwgZ3JvdXAgPSAiT3BlbiBTdHJlZXQgTWFwcyIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXIsIGdyb3VwID0gIlN0YW1lbiBUb25lciIpICU+JQ0KICBhZGRDaXJjbGVzKGRhdGEgPSAoYmVldGxlICU+JSBmaWx0ZXIoTWFsZXMgPiBGZW1hbGVzKSksIA0KICAgICAgICAgICAgIGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCByYWRpdXMgPSAxMDAwMCwgd2VpZ2h0ID0gMSwNCiAgICAgICAgICAgICBncm91cCA9ICJtYWxlcyIpICU+JQ0KICBhZGRDaXJjbGVzKGRhdGEgPSAoYmVldGxlICU+JSBmaWx0ZXIoTWFsZXMgPCBGZW1hbGVzKSksIA0KICAgICAgICAgICAgIGZpbGxDb2xvciA9ICJwaW5rIiwgZmlsbE9wYWNpdHkgPSAxLCANCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCByYWRpdXMgPSAxMDAwMCwgd2VpZ2h0ID0gMSwNCiAgICAgICAgICAgICBncm91cCA9ICJmZW1hbGVzIikgJT4lDQogIGFkZExheWVyc0NvbnRyb2wob3ZlcmxheUdyb3VwcyA9IGMoIm1hbGVzIiwgImZlbWFsZXMiKSwNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpLA0KICAgICAgICAgICAgICAgICAgIGJhc2VHcm91cHMgPSBjKCJEZWZhdWx0IiwgIk9wZW4gU3RyZWV0IE1hcHMiLCAiU3RhbWVuIFRvbmVyIikpDQpgYGANCg0KTm90aWNlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gb3ZlcmxheSBncm91cHMgKGNpcmNsZXMpIGFuZCBiYXNlIGdyb3VwcyAoYmFzZW1hcHMpDQoNCg0KIyMjIFpvb20gTGV2ZWxzDQoNCldhbnQgdG8gZGlzcGxheSBtb3JlIGRldGFpbCB3aGVuIHRoZSBtYXAgaW4gem9vbWVkIGluIGFuZCBsZXNzIGRldGFpbCB3aGVuIHRoZSBtYXAgaXMgem9vbWVkIG91dD8gVGhpcyBpcyBkb25lIHdpdGggYHpvb21MZXZlbHNgIGluIHRoZSBgZ3JvdXBPcHRpb25zKClgDQpgYGB7cn0NCmxlYWZsZXQoYmVldGxlKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZXMoZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgDQogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gImJlZXRsZXMiKSAlPiUNCiAgZ3JvdXBPcHRpb25zKCJiZWV0bGVzIiwgem9vbUxldmVscyA9IDU6OCkNCmBgYA0KWm9vbSBpbiBhbmQgb3V0IHRvIHdhdGNoIHRoZSBsYXllciB0dXJuIG9uL29mZg0KDQoNCiMjIyBDbHVzdGVyaW5nDQoNCg0KV2hlbiB0aGVyZSBhcmUgYSBsYXJnZXIgbnVtYmVyIG9mIG1ha2VycyBvbiBhIG1hcCB5b3UgY2FuIGNsdXN0ZXIgdGhlbSB1c2luZyBgY2x1c3Rlck9waW9ucyA9YA0KDQpBIGNvbW1vbiBleGFtcGxlIG9mIGNsdXN0ZXJpbmcgaXMgdGhlIFtEb21pbmlvbiBQb3dlciBvdXRhZ2UgbWFwXShodHRwczovL291dGFnZW1hcC5kb21pbmlvbmVuZXJneS5jb20vZXh0ZXJuYWwvZGVmYXVsdC5odG1sKS4NCg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gImJlZXRsZXMiLA0KICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoKSkgDQpgYGANCkhvdmVyaW5nIG92ZXIgYSBjbHVzdGVyIG1hcmtlciB3aXRoIHlvdXIgbW91c2UgYWxsb3dzIHlvdSB0byBzZWUgdGhlIGNvdmVyYWdlIG9mIHRoZSBjbHVzdGVyDQoNCkNsaWNrIG9uIGEgY2x1c3RlciB0byB6b29tIGluIHRvIHRoZSBjbHVzdGVyIGJvdW5kcw0KDQpBbGwgb2YgdGhlc2UgZmVhdHVyZXMgY2FuIGJlIGNoYW5nZWQgYG1hcmtlckNsdXN0ZXJPcHRpb25zYC4gYG1hcmtlckNsdXN0ZXJPcHRpb25zYCBhbHNvIGFsbG93cyB5b3UgdG8gZnJlZXplIHRoZSBjbHVzdGVyaW5nIGF0IGEgZGVmaW5lZCB6b29tIGxldmVsIHdpdGggYGZyZWV6ZUF0Wm9vbSA9IGANCg0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gImJlZXRsZXMiLA0KICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMoDQogICAgICAgICAgICAgICBzaG93Q292ZXJhZ2VPbkhvdmVyID0gRkFMU0UsDQogICAgICAgICAgICAgICBmcmVlemVBdFpvb20gPSA2KSkgDQpgYGANCk5vdyB0aGUgY2x1c3RlcnMgc3RheSB0aGUgc2FtZSByZWdhcmRsZXNzIG9mIHpvb20gbGV2ZWwuIFdoZW4gdGhlIGNsdXN0ZXIgaXMgY2xpY2tlZCB5b3UgY2FuIHNlZSBlYWNoIHBvaW50IGluY2x1ZGVkIGluIHRoZSBjbHVzdGVyDQoNCiMjIExlZ2VuZHMNCg0KTGVnZW5kcyBhcmUgYWRkZWQgdXNpbmcgYGFkZExlZ2VuZCgpYA0KDQotIFlvdSBtdXN0IHByb3ZpZGUgY29sb3IgaW5mb3JtYXRpb24NCiAgDQogIC0gYHBhbCA9YCBpZiB1c2luZyBhIHBhbGV0dGUsIG9yIGBjb2xvciA9IGAgaWYgdXNpbmcgY3VzdG9tIGRlZmluZWQgY29sb3JzDQoNCi0gWW91IG11c3QgcHJvdmlkZSB0aGUgdmFsdWVzIHVzZWQgdG8gZ2VuZXJhdGUgY29sb3JzIGZyb20gdGhlIHBhbGV0dGUgb3IgdGhlIGxhYmVscyBmb3IgZWFjaCBjb3JyZXNwb25kaW5nIGNvbG9yDQoNCg0KVGhlIGV4YW1wbGUgYmVsb3cgc2hvd3MgYSBsZWdlbmQgd2l0aCBjYXRlZ29yaWNhbCBkYXRhDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA+IEZlbWFsZXMpKSwgDQogICAgICAgICAgICAgZmlsbENvbG9yID0gImxpZ2h0Ymx1ZSIsIGZpbGxPcGFjaXR5ID0gMSwgDQogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gIm1hbGVzIikgJT4lDQogIGFkZENpcmNsZXMoZGF0YSA9IChiZWV0bGUgJT4lIGZpbHRlcihNYWxlcyA8IEZlbWFsZXMpKSwgDQogICAgICAgICAgICAgZmlsbENvbG9yID0gInBpbmsiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHJhZGl1cyA9IDEwMDAwLCB3ZWlnaHQgPSAxLA0KICAgICAgICAgICAgIGdyb3VwID0gImZlbWFsZXMiKSAlPiUNCiAgYWRkTGVnZW5kKCBjb2xvcnMgPSBjKCJsaWdodGJsdWUiLCAicGluayIpLA0KICAgICAgICAgICAgIGxhYmVscyA9IGMoIk1vcmUgTWFsZXMiLCAiTW9yZSBGZW1hbGVzIiksDQogICAgICAgICAgICAgb3BhY2l0eSA9IDEpDQpgYGANCiAgIA0KVGhlIG5leHQgZXhhbXBsZSBzaG93cyBhIGxlZ2VuZCB3aXRoIGNvbnRpbnVvdXMgZGF0YS4gTm90aWNlIHRoYXQgYHBhbGAgaXMgdXNlZCB0aGlzIHRpbWUgaW5zdGVhZCBvZiBgY29sb3JgIGFuZCBgdmFsdWVzYCBpbnN0ZWFkIG9mIGBsYWJlbHNgDQpgYGB7cn0NCnBhbCA8LSBjb2xvck51bWVyaWMoDQogIHBhbGV0dGUgPSAiUmRCdSIsDQogIGRvbWFpbiA9IGJlZXRsZSRNRlJhdGlvKQ0KDQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVzKGZpbGxDb2xvciA9IH5wYWwoTUZSYXRpbyksIGZpbGxPcGFjaXR5ID0gLjcsIHJhZGl1cyA9IDE1MDAwLCANCiAgICAgICAgICAgICB3ZWlnaHQgPSAxLCBjb2xvciA9ICJncmV5IikgJT4lDQogIGFkZExlZ2VuZChwYWwgPSBwYWwsIHZhbHVlcyA9IH5NRlJhdGlvLA0KICAgICAgICAgICAgdGl0bGUgPSAiTWFsZSB0byBGZW1hbGUgUmF0aW8iKQ0KYGBgDQoNCg0KDQojIyBTY2FsZSBCYXJzDQoNClNjYWxlIGJhcnMgYXJlIGFkZGVkIHVzaW5nIGBhZGRTY2FsZUJhcigpYC4gVGhlIHNjYWxlIGJhciB3aWxsIGFkanVzdCBpdHNlbGYgYXMgeW91IHpvb20gaW4gYW5kIG91dA0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxKSAlPiUNCiAgYWRkU2NhbGVCYXIocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQ0KYGBgDQoNCg0KWW91IGNhbiBjaG9vc2UgdG8gdXNlIG1ldHJpYyBhbmQvb3IgaW1wZXJpYWwgdW5pdHMgYW5kIGFkanVzdCB0aGUgc2NhbGUgYmFyIHdpZHRoIHVzaW5nIGBvcHRpb25zID0gc2NhbGVCYXJPcHRpb25zYC4gDQoNCg0KIyMgTGVhZmxldCBFeHRyYXMgUGFja2FnZQ0KDQpUaGUgYGxlYWZsZXQuZXh0cmFzYCBwYWNrYWdlIGFsbG93cyBmb3IgYWRkaXRpb25hbCBmdW5jdGlvbmFsaXR5IHZpYSBsZWFmbGV0IHBsdWdpbnMuIFdlJ2xsIHRha2UgYSBsb29rIGF0IHRocmVlIGV4YW1wbGVzIGhlcmUsIGJ1dCB0aGVyZSBhcmUgW3RvbnMgb2YgcGx1Z2luc10oaHR0cHM6Ly9iaGFza2FydmsuZ2l0aHViLmlvL2xlYWZsZXQuZXh0cmFzLykgdG8gY2hvb3NlIGZyb20gDQoNCiMjIyBSZXNldA0KDQpgYWRkUmVzZXRNYXBCdXR0b24oKWAgcmVzZXRzIHlvdSBtYXAgdG8gdGhlIG9yaWdpbmFsIHZpZXcgYW5kIHpvb20gbGV2ZWwNCmBgYHtyfQ0KbGlicmFyeShsZWFmbGV0LmV4dHJhcykNCg0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhmaWxsQ29sb3IgPSAibGlnaHRibHVlIiwgZmlsbE9wYWNpdHkgPSAxLCANCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgd2VpZ2h0ID0gMSkgJT4lDQogIGFkZFJlc2V0TWFwQnV0dG9uKCkNCmBgYA0KTW92ZSB0aGUgbWFwIGFuZCB6b29tIGluL291dCwgdGhlbiBwcmVzcyB0aGUgcmVzZXQgYnV0dG9uIHRvIG1ha2UgdGhlIG1hcCByZXR1cm4gdG8gaXRzIHN0YXJ0aW5nIHZpZXcgYW5kIHpvb20gbGV2ZWwNCg0KDQojIyMgU2VhcmNoIEJhcnMNCg0KYGFkZFNlYXJjaE9TTSgpYCBhbmQgYGFkZFNlYXJjaEdvb2dsZSgpYCBhbGxvdyB5b3UgdG8gc2VhcmNoIGZvciBsb2NhdGlvbnMgb24gdGhlIG1hcA0KYGBge3J9DQpsZWFmbGV0KGJlZXRsZSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGZpbGxDb2xvciA9ICJsaWdodGJsdWUiLCBmaWxsT3BhY2l0eSA9IDEsIA0KICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCB3ZWlnaHQgPSAxKSAlPiUNCiAgYWRkU2VhcmNoT1NNKG9wdGlvbnMgPSBzZWFyY2hPcHRpb25zKA0KICAgIHpvb20gPSA4LA0KICAgIGhpZGVNYXJrZXJPbkNvbGxhcHNlID0gVFJVRSwNCiAgICBhdXRvQ29sbGFwc2UgPSBGQUxTRSkpDQpgYGANCiAgIA0KDQpUcnkgc2VhcmNoaW5nIGZvciAiQ2FibyBTYW4gTHVjYXMiDQoNCiMjIyBEcmF3IFRvb2xiYXINCg0KYGFkZERyYXdUb29sYmFyKClgIGFsbG93cyB5b3UgdG8gZHJhdyBwb2ludHMsIGxpbmVzIGFuZCBwb2x5Z29ucyBvbiBtYXANCmBgYHtyfQ0KbGVhZmxldChiZWV0bGUpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhmaWxsQ29sb3IgPSAibGlnaHRibHVlIiwgZmlsbE9wYWNpdHkgPSAxLCANCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgd2VpZ2h0ID0gMSkgJT4lDQogIGFkZERyYXdUb29sYmFyKA0KICAgIGVkaXRPcHRpb25zID0gZWRpdFRvb2xiYXJPcHRpb25zKA0KICAgICAgc2VsZWN0ZWRQYXRoT3B0aW9ucyA9IHNlbGVjdGVkUGF0aE9wdGlvbnMoKSApKSAlPiUNCiAgcmVtb3ZlRHJhd1Rvb2xiYXIoKQ0KYGBgDQpUcnkgZHJhd2luZyBhbmQgdGhlbiBkZWxldGluZyBhIHNoYXBlIGZyb20geW91ciBtYXANCg0KYGFkZFN0bHllRWRpdG9yKClgIGFsbG93cyB5b3UgdG8gZWRpdCB0aGUgYXBwZWFyYW5jZSBvZiBzaGFwZXMNCg0KDQoNCiMjIE1ha2luZyBhIENob3JvcGxldGggTWFwDQoNCkxpbmVzIGFuZCBQb2x5Z29ucyB3b3JrIGluIGEgdmVyeSBzaW1pbGFyIG1hbm5lciB0byBjaXJjbGVzDQoNCkZvciB0aGlzIGV4YW1wbGUgd2UnbGwgdXNlIGNlbnN1cyB0cmFjdHMgZnJvbSB0aGUgYHRpZ3Jpc2AgcGFja2FnZQ0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0aWdyaXMpDQpvcHRpb25zKHRpZ3Jpc191c2VfY2FjaGUgPSBUUlVFKQ0KDQp0cmFjdHMgPC0gdHJhY3RzKCJWQSIsICJSaWNobW9uZCBjaXR5IikgJT4lDQogIHN0X3RyYW5zZm9ybSg0MzI2KQ0KYGBgDQoNCkRlZmluaW5nIHRoZSBjb2xvciBwYWxldHRlIHVzaW5nIHRoZSBsYW5kIGFyZWEgb2YgZWFjaCB0cmFjdC4gQXJlYSBpcyBnaXZlbiBpbiBzcXVhcmUgbWV0ZXJzLiBUbyBjb252ZXJ0IHRvIHNxdWFyZSBtaWxlcyB3ZSBkaXZpZGUgb3VyIGFyZWEgYnkgMi41OWUrNg0KYGBge3J9DQpwYWwgPC0gY29sb3JOdW1lcmljKHBhbGV0dGUgPSAiQmx1ZXMiLA0KICAgICAgICAgICAgICAgICAgICBkb21haW4gPSB0cmFjdHMkQUxBTkQvMi41OWUrNikgDQpgYGANCg0KVGhlIHBvbHlnb24gY29sb3JzIGFyZSBhc3NpZ25lZCB1c2luZyBgZmlsbENvbG9yID0gfnBhbChBTEFORC8yLjU5ZSs2KWANCg0KTGFiZWxzLCBsZWdlbmQgYW5kIHNjYWxlIGJhciBhcmUgYWRkZWQgdG8gZ2l2ZSB0aGUgbWFwIGEgbW9yZSBwb2xpc2hlZCBmaW5pc2gNCmBgYHtyIGVjaG89VFJVRX0NCmxlYWZsZXQodHJhY3RzKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lDQogIGFkZFBvbHlnb25zKGZpbGxDb2xvciA9IH5wYWwoQUxBTkQvMi41OWUrNiksICBmaWxsT3BhY2l0eSA9IDEsIHNtb290aEZhY3RvciA9IC4yLA0KICAgICAgICAgICAgICBjb2xvciA9ICJkYXJrZ3JleSIsIHdlaWdodCA9IDEsDQogICAgICAgICAgICAgICAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMoY29sb3IgPSAid2hpdGUiLCB3ZWlnaHQgPSAyLCBvcGFjaXR5ID0gMSwNCiAgICAgICAgICAgICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwNCiAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZShyb3VuZCh0cmFjdHMkQUxBTkQvMi41OWUrNiwgZGlnaXRzID0gMiksICJzcS4gbWkuIikNCiAgICAgICAgICAgICAgKSAlPiUNCiAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfkFMQU5ELzIuNTllKzYsIHRpdGxlID0gIkFyZWEgKHNxLiBtaWxlcykiKSAlPiUNCiAgYWRkU2NhbGVCYXIocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiKQ0KYGBgDQpUaGUgbWFwIGlzIG5vdyBzaGFkZWQgc28gdGhhdCB0aGUgbGFyZ2VyIGNlbnN1cyB0cmFjdHMgYXJlIGRhcmtlciBibHVlIGFuZCB0aGUgc21hbGxlc3QgdHJhY3RzIGFyZSBsaWdodGVyIGJsdWUNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==